/**
 * 
 */
package com.ezops.filter;


import java.io.IOException;
import java.util.Date;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * <p>Tracks the user agent and validates the session originator.</p>
 * 
 * <p>The {@link HttpSession} is validated by comparing a token that is placed in the session upon creation against
 * a permanent cookie that acts as a unique user-agent (browser) identifier.  If the token does not match, then
 * it means that a URL containing the session id has migrated from one user-agent to another, which is not permitted
 * for security reasons.  In that case, an error is displayed notifying the user that this operation is
 * not allowed and requiring the user to login again.</p>
 * <p>If the session is new, then the user-agent is assigned an identifier (if it hasn't already been assigned one) and
 * stores that identifier as a token in the session, marking this user-agent as the session originator.</p>
 * 
 * @web.filter
 *   name="SessionTrackingFilter"
 *   description="Tracks the user agent and validates the session originator"
 * @web.filter-mapping
 *   url-pattern="*."
 *   dispatcher="REQUEST"
 * @author Administrator
 *
 */
public class SessionTrackingFilter implements Filter {

	private static Log logger = LogFactory.getLog( SessionTrackingFilter.class );

	private static final String FILTER_APPLIED = "__session_tracking_filter_applied";

	private static final String USER_AGENT_ID_COOKIE_NAME = "UAID";

	private static final String SESSION_ORIGINATOR_ATTR = "__session_originator";

	public void init( FilterConfig config ) throws ServletException {
	}

	public void doFilter( ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain ) throws IOException,
	ServletException {

		if ( servletRequest.getAttribute( FILTER_APPLIED ) != null ) {
			chain.doFilter( servletRequest, servletResponse );
			return;
		}

		if ( !(servletRequest instanceof HttpServletRequest ) || !(servletResponse instanceof HttpServletResponse) ) {
			chain.doFilter( servletRequest, servletResponse );
			return;
		}

		HttpServletRequest request = (HttpServletRequest) servletRequest;
		HttpServletResponse response = (HttpServletResponse) servletResponse;

		if ( request.getAttribute( FILTER_APPLIED ) == null ) {

			// see if the browser uid is the same as the session originator; if not, deny access
			if ( request.getSession( false ) != null && !request.getSession().isNew() && !isSessionOriginator( request ) ) {
				if ( logger.isDebugEnabled() ) {
					logger.debug( "Preventing access to session since user agent is not the originator" );
				}

				// QUESTION: should recontructing the url be done in a utility instead?
				StringBuffer url = request.getRequestURL();

				// strip off the session id part if it came with requestURL (jetty does this)
				if (url.indexOf( ";jsessionid=" ) != -1) {
					url.delete( url.indexOf( ";jsessionid=" ), url.length() );
				}

				if ( !StringUtils.isEmpty( request.getQueryString() ) ) {
					url.append( "?" );
					url.append( request.getQueryString() );
				}

				response.sendRedirect( url.toString() );

				// QUESTION: should we instead send either 203 (bad meta), 400 (bad request) or 412 (precondition)
				//response.sendError( HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "The URL you requested is attempting to use a session that was originated by another computer (or browser).  You will be required to login again." );
				return;
			}

			// create a new session if one does not exist or if it is still unclaimed
			if ( request.getSession( true ).isNew() || request.getSession().getAttribute( SESSION_ORIGINATOR_ATTR ) == null ) {
				// take ownership over this session
				bindToSession( request, response );
			}

			request.setAttribute( FILTER_APPLIED, Boolean.TRUE );
		}

		chain.doFilter( servletRequest, servletResponse );
	}

	private boolean isSessionOriginator( HttpServletRequest request ) {
		// check if session is still unclaimed (created somewhere on the way to this filter)
		if ( request.getSession().getAttribute( SESSION_ORIGINATOR_ATTR ) == null ) {
			return true;
		}

		String userAgentId = getUserAgentId( request );
		return ( userAgentId != null && userAgentId.equals( request.getSession().getAttribute( SESSION_ORIGINATOR_ATTR ) ) );
	}

	private void bindToSession( HttpServletRequest request, HttpServletResponse response ) {
		String userAgentId = getUserAgentId( request );
		System.out.println("-------------------------------------Session------------------------");
		
		System.out.println("userAgentId1 : " + userAgentId);
		if ( StringUtils.isEmpty( userAgentId ) ) {
			userAgentId = generateUserAgentId( request );
			System.out.println("userAgentId2 : " + userAgentId);
			System.out.println("-------------------------------------Session------------------------");
			
			Cookie c = new Cookie( USER_AGENT_ID_COOKIE_NAME, userAgentId );
			// 5 years...should be plenty
			c.setMaxAge( 60 * 60 * 24 * 365 * 5 );
			c.setPath( "/" );
			c.setSecure( request.isSecure() );
			response.addCookie( c );
		}

		request.getSession().setAttribute( SESSION_ORIGINATOR_ATTR, userAgentId );
	}

	private String getUserAgentId( HttpServletRequest request ) {
		if ( request.getCookies() == null ) {
			return null;
		}

		for ( Cookie cookie : request.getCookies() ) {
			if ( cookie.getName().equals( USER_AGENT_ID_COOKIE_NAME ) ) {
				return cookie.getValue();
			}
		}

		return null;
	}

	/**
	 * Using the message digest algorithm, generate a unique identifier for this browser,
	 * using the server domain name, a random number and the current time in milliseconds
	 * as the salts.
	 */
	private String generateUserAgentId( HttpServletRequest request ) {
		StringBuffer userAgentId = new StringBuffer();
		userAgentId.append( DigestUtils.md5Hex( request.getRemoteAddr() ) );
		userAgentId.append( "." );
		userAgentId.append( DigestUtils.md5Hex( String.valueOf( Math.round( Math.random() * 2147483647 ) ) ) );
		userAgentId.append( "." );
		userAgentId.append( DigestUtils.md5Hex( String.valueOf( Math.round( new Date().getTime() / 1000 ) ) ) );
		return userAgentId.toString();
	}

	public void destroy() {
	}

}

